<?php

namespace app\index\controller;

use app\common\logic\water\ShelfLogic;
use think\Controller;
use think\facade\Log;
use app\common\model\WaterRack;
use app\common\model\Member;
use GuzzleHttp\Client;
use app\common\utils\WechatUtil;

class Mqtt extends Controller
{
    protected $token = 'vp6axzp4evr62tmz';

    protected $project_id = '4e3a6e1599414182b9b928e16da7a5b8'; //项目id华东-上海一

    protected function initialize()
    {
        Log::init(['type' => 'File', 'path' => app()->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR . 'mqtt']);
    }

    /**
     * 监听mqtt发布消息
     *
     * @return mixed
     */
    public function message()
    {
        if (!$this->validateToken()) {
            trace('authorization failed');
            return json(['code' => 1, 'msg' => '参数错误']);
        }

        $post = input('post.');

        if ($post['resource'] == 'device.message.status' && $post['event'] == 'update') {
            //notify 下发消息以后，设备返回的消息，不用回复了，不然无限循环
            return 'success';
        }

        if (isset($post['notify_data']) && $post['resource'] == 'device.property' && $post['event'] == 'report') {
            $this->devicePropertyReport($post['notify_data']);
        } else {
            trace($post);
        }

        if (isset($post['notify_data']) && isset($post['notify_data']['header']) && isset($post['notify_data']['header']['device_id'])) {
            $this->notify($post['notify_data']['header']['device_id'], $post['request_id']);
        }

        return 'success';
    }

    protected function devicePropertyReport($notifyData)
    {
        $device_id = $notifyData['header']['device_id'];
        $services = $notifyData['body']['services'] ?? [];
        $device = WaterRack::where('out_device_sid', $device_id)->find();
        if (!$device) {
            trace('设备不存在' . $device_id);
            return;
        }

        if (!$services) {
            trace('没有服务数据');
            return;
        }

        $serv = array_pop($services);

        $full_num = 0;
        $empty_num = 0;

        //水架推送库存 格式mqtdata/pushInventory.json
        if ($serv && $serv['service_id'] == 'pushInventory') { //水架推送库存
            $properties = $serv['properties'];
            $data = [
                'csq' => $properties['CSQ'],
                'push_type' => $properties['Type'],
                'push_time' => date('Y-m-d H:i:s'),
                'battery_voltage' => $properties['BatteryVoltage'],
                'dump_energy' => $this->dumpEnergy($properties['BatteryVoltage']),
                'firmware' => $properties['firmware'],
                'geo_location' => implode(',', $properties['GeoLocation']),
                'heartbeat_interval' => $properties['HeartbeatInterval'],
            ];

            $Inventory = $properties['Inventory'] ?? [];
            foreach ($Inventory as $key => $value) {
                // C01 - C12
                if (preg_match('/^c\d+$/i', $key, $match)) {
                    $data[strtolower($key)] = $value;
                    if ($value == 1) {
                        $full_num += 1;
                    }
                }
            }

            $empty_num = $device['grids_num'] - $full_num; //空桶数量 = 格子数 - 满同数量
            if ($empty_num < 0) {
                $empty_num = 0;
            }

            if (
                $empty_num > 0
                // && $data['empty_num'] < $empty_num//空桶数量增加才通知
            ) {
                $openids = Member::where('id', 'in', [$device->member_id, $device->agent_id])->column('openid');

                $openids = array_merge($openids, [
                    'o5-5f7CbympdCl-V3qIBQ9-3So7I', //lhy
                    'o5-5f7F1LcfCbVn74Y7tlPxaxo7Q', //wwd
                    'o5-5f7HfAAKF-7flQrRHgpJwlS9Q', //wyh
                ]);

                $openids = array_unique($openids);

                $this->emptyBucketNofity($openids, $device->rack_no, $empty_num); //空桶通知
            }

            $data['empty_num'] = $empty_num;
            $device->save($data);
        }
        if ($serv && $serv['service_id'] == 'LowBattery') { //电压低
            $properties = $serv['properties'];
            $data = [
                'push_time' => date('Y-m-d H:i:s'),
                'battery_voltage' => $properties['Voltage'],
                'dump_energy' => $this->dumpEnergy($properties['Voltage']),
            ];
            $device->save($data);

            $openids = Member::where('id', 'in', [$device->member_id, $device->agent_id])->column('openid');

            $openids = array_merge($openids, [
                'o5-5f7CbympdCl-V3qIBQ9-3So7I', //lhy
                'o5-5f7F1LcfCbVn74Y7tlPxaxo7Q', //wwd
                'o5-5f7HfAAKF-7flQrRHgpJwlS9Q', //wyh
            ]);

            $openids = array_unique($openids);

            //知更换电池 
            $this->lowBatteryNofity($openids, $device->rack_no, $properties['Voltage']);
        }

        if ($serv) {
            sleep(rand(1, 9));// 暂停1-9秒
            // 更新客户架子库存信息
            (new ShelfLogic())->updateShelfInventory($device_id, $full_num);
        }
    }

    /**
     * 计算剩余电量
     * @param mixed $battery_voltage
     * @return int
     */
    public function dumpEnergy($battery_voltage)
    {
        $max = 3.9; //最大电量 (充满电压4.2v，但掉的快)
        $min = 3.0; //最小电量
        $size = $max - $min; //电量范围

        if ($battery_voltage > $max) {
            $battery_voltage = $max;
        } else if ($battery_voltage < $min) {
            $battery_voltage = $min;
        }

        $leave = $battery_voltage - $min; //剩余电量
        if ($leave <= 0) {
            $leave = 0.01;
        }
        $percent = $leave / $size; //剩余电量百分比

        return (int)($percent * 100);
    }

    /**
     * Summary of lowBatteryNofity
     * @param array $openids
     * @param string $rack_no
     * @param number $battery_voltage
     * @return void
     */
    protected function lowBatteryNofity($openids, $rack_no, $battery_voltage)
    {
        $accToken = WechatUtil::getAccessToken(env('wechat_appid'), env('wechat_appsecret'));

        if (!$accToken) {
            return;
        }

        foreach ($openids as $openid) {
            if (!$openid) {
                continue;
            }
            $data = [
                'template_id' => '4op09OiE8fx94em2HxetzpXhemg0D_gP_j58n9IJ0s4',
                'page' => 'pages/index/index',
                'touser' => $openid,
                'data' => [
                    'thing1' => [
                        //设备名
                        'value' => '智能水架',
                    ],
                    'thing2' => [
                        //异常描述
                        'value' => '电压低',
                    ],
                    'character_string3' => [
                        //设备编号
                        'value' => $rack_no, //32位以内
                    ],
                    'time5' => [
                        //时间
                        'value' => date('Y-m-d H:i:s'),
                    ],
                    'phrase9' => [
                        //设备状态
                        'value' => $battery_voltage . 'V',
                    ],
                ],
                'miniprogram_state' => 'formal', //跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
                'lang' => 'zh_CN'
            ];

            WechatUtil::sendMessage($data, $accToken);
        }
    }

    /**
     * Summary of emptyBucketNofity
     * @param array $openids
     * @param string $rack_no
     * @param int $empty_num
     * @return void
     */
    protected function emptyBucketNofity($openids, $rack_no, $empty_num)
    {
        $accToken = WechatUtil::getAccessToken(env('wechat_appid'), env('wechat_appsecret'));

        if (!$accToken) {
            return;
        }

        foreach ($openids as $openid) {
            $data = [
                'template_id' => 'Ecp3ZMvlLJrSy3gv04rcgE1h3MjdXIedNquBG4wk8E4',
                'page' => 'pages/index/index',
                'touser' => $openid,
                'data' => [
                    'thing4' => [
                        //设备编号
                        'value' => $rack_no, //32位以内
                    ],
                    'number3' => [
                        //缺货数量
                        'value' => $empty_num,
                    ],
                    'thing6' => [
                        //备注
                        'value' => '请尽快补充水',
                    ],
                    'thing7' => [
                        //设备名称
                        'value' => '智能水架',
                    ],
                ],
                'miniprogram_state' => 'formal', //跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
                'lang' => 'zh_CN'
            ];

            WechatUtil::sendMessage($data, $accToken);
        }
    }

    /**
     * 监听mqtt 连接/断 开事件
     *
     * @return mixed
     */
    public function event()
    {
        if (!$this->validateToken()) {
            trace('authorization failed');
            return json(['code' => 1, 'msg' => '参数错误']);
        }

        $post = input('post.');
        // trace($post);
        if (isset($post['notify_data'])) {
            if ($post['notify_data']['body']['status'] == 'ONLINE') {
                trace('连接');
            } else if ($post['notify_data']['body']['status'] == 'OFFLINE') {
                trace('断开连接');
            } else {
                trace($post['notify_data']['body']['status']);
            }
        } else {
            trace($post);
        }

        return 'success';
    }

    protected function validateToken()
    {
        // if (in_array(request()->ip(), ['127.0.0.1', '211.149.174.119'])) {
        //     return true;
        // }

        $timestamp = request()->header('timestamp');
        $nonce = request()->header('nonce');
        $signature = request()->header('signature');

        if (empty($timestamp) || empty($nonce) || empty($signature)) {
            return false;
        }
        /**
         * 将token、timestamp、nonce进行字典排序。
         * 将排序后的字符串进行sha256加密。
         * 客户收到推送信息后，可以根据token以及头域中的timestamp和nonce按照该规则进行加密后与头域中的signature进行比较来确认是否是平台的消息。
         */
        $tmpArr = [$this->token, $timestamp, $nonce];
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = hash('sha256', $tmpStr);
        if ($tmpStr === $signature) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Summary of notify
     * @param string $device_id
     * @param string $request_id
     * @return void
     */
    protected function notify($device_id, $request_id)
    {
        $url = "https://b545d65bd6.st1.iotda-app.cn-east-3.myhuaweicloud.com/v5/iot/{$this->project_id}/devices/{$device_id}/messages";
        $token = $this->getApiToken();
        if (!$token) {
            trace('获取token失败');
            return;
        }

        $headers = [
            'Content-Type' => 'application/json;charset=utf8',
            'X-Auth-Token' => $token
        ];
        $params = [
            // 'message_id' => 'reply-' . $request_id,
            // 'name' => 'message_received',
            'message' => 'UPDATE OK',
            // 'topic' => "\$oc/devices/{$device_id}/sys/messages/down",
        ];

        $res = $this->httpPost($url, 'POST', $params, $headers);

        if ($res['code'] == 1) {
            trace('通知成功');
        } else {
            trace('通知失败' . $res['msg']);
        }
    }

    /**
     * 获取api token
     * @see https://support.huaweicloud.com/api-iam/iam_30_0001.html#iam_30_0001__zh-cn_topic_0221482476_response_Rs31Token
     * @return string
     */
    protected function getApiToken()
    {
        if ($token = cache('hw_api_token')) {
            return $token;
        }

        $url = 'https://iam.cn-east-3.myhuaweicloud.com/v3/auth/tokens'; //华东-上海一url
        $params = [
            'auth' => [
                'identity' => [
                    'methods' => ['password'],
                    'password' => [
                        'user' => [
                            'name' => 'bjy_device_api', //账号
                            'password' => 'zBq39sFuzgAP9S7834Qdk', //密码
                            'domain' => [
                                'name' => 'hw085038823'
                            ]
                        ]
                    ]
                ],
                'scope' => [
                    'project' => [
                        'id' => $this->project_id
                    ]
                ]
            ]
        ];
        $headers = [
            'Content-Type' => 'application/json;charset=utf8',
        ];
        $res = $this->httpGetToken($url, 'POST', $params, $headers);

        if ($res['code'] == 1) {
            cache('hw_api_token', $res['token'], 60 * 60 * 23);
            return $res['token'];
        }

        return '';
    }

    /**
     * CURL请求
     * @param $url 请求url地址
     * @param $method 请求方法 get post
     * @param array|mixed $postfields post数据数组
     * @param array $headers 请求header信息
     * @param bool|false $debug 调试开启 默认false
     * @return mixed
     */
    protected function httpGetToken($url, $method, $postfields = [], $headers = [], $debug = false, $tryTime = 0)
    {
        $client = new Client([
            'timeout' => 30,
            'verify' => false
        ]);
        try {
            $response = $client->request($method, $url, ['headers' => $headers, 'json' => $postfields]);
            if ($debug) {
                trace($response->getStatusCode()); // 200
                trace($response->getBody()->__toString());
                trace($response->getHeaders());
            }
            if ($response->getStatusCode() == 201) {
                return ['code' => 1, 'token' => $response->getHeader('X-Subject-Token')];
            }
            return ['code' => 0, 'msg' => $response->getBody()->__toString()];
        } catch (\Throwable $e) {
            trace($e->getMessage());
            if ($tryTime < 3) {
                $tryTime += 1;
                usleep(100000); //0.1s
                return self::httpGetToken($url, $method, $postfields, $headers, $debug, $tryTime);
            } else {
                return ['code' => 0, 'msg' => $e->getMessage()];
            }
        }
    }

    /**
     * CURL请求
     * @param $url 请求url地址
     * @param $method 请求方法 get post
     * @param array|mixed $postfields post数据数组
     * @param array $headers 请求header信息
     * @param bool|false $debug 调试开启 默认false
     * @return mixed
     */
    protected function httpPost($url, $method, $postfields = [], $headers = [], $debug = false, $tryTime = 0)
    {
        $client = new Client([
            'timeout' => 30,
            'verify' => false
        ]);
        try {
            $response = $client->request($method, $url, ['headers' => $headers, 'json' => $postfields]);
            if ($debug) {
                trace($response->getStatusCode()); // 200
                trace($response->getBody()->__toString());
                trace($response->getHeaders());
            }
            return ['code' => 1, 'msg' => $response->getBody()->__toString()];
        } catch (\Throwable $e) {
            trace($e->getMessage());
            if ($tryTime < 3) {
                $tryTime += 1;
                usleep(100000); //0.1s
                return self::httpGetToken($url, $method, $postfields, $headers, $debug, $tryTime);
            } else {
                return ['code' => 0, 'msg' => $e->getMessage()];
            }
        }
    }
}
